local key = {}
local config = require "config"

local function to_ctrl_key(ch)
    return string.byte(ch) & 0x1F
end

local ARROW_UP    = "\x1b[A"
local ARROW_DOWN  = "\x1b[B"
local ARROW_RIGHT = "\x1b[C"
local ARROW_LEFT  = "\x1b[D"
local ENTER       = "\r"
local EXIT        = to_ctrl_key("e")    -- ctrl+e
local BACKSPACE   = 0x7F
local TAB         = 0x09
local SAVE        = to_ctrl_key("s")    -- ctrl+s

-- key type
local KEY_ERROR       = 999
local KEY_COMMON      = 1000
local KEY_ARROW_LEFT  = 1001
local KEY_ARROW_RIGHT = 1002
local KEY_ARROW_UP    = 1003
local KEY_ARROW_DOWN  = 1004
local KEY_BACKSPACE   = 1005
local KEY_ENTER       = 1006
local KEY_TAB         = 1007
local KEY_EXIT        = 1008
local KEY_SAVE        = 1009

-- store the read keys
local read_keys = {}

-- what type of the pressed key
local function what_type()
    if #read_keys == 1 then
        local ch = read_keys[1]
        if string.byte(ch) == EXIT then
            return KEY_EXIT
        elseif string.byte(ch) == BACKSPACE then
            return KEY_BACKSPACE
        elseif string.byte(ch) == TAB then
            return KEY_TAB
        elseif string.byte(ch) == SAVE then
            return KEY_SAVE
        elseif string.byte(ch) >= 32 and
           string.byte(ch) <= 126 then
            return KEY_COMMON
        elseif ch == ENTER then
            return KEY_ENTER
        end
    else
        local res = table.concat(read_keys)
        if res == ARROW_DOWN then
            return KEY_ARROW_DOWN
        elseif res == ARROW_UP then
            return KEY_ARROW_UP
        elseif res == ARROW_LEFT then
            return KEY_ARROW_LEFT
        elseif res == ARROW_RIGHT then
            return KEY_ARROW_RIGHT
        end
    end

    return KEY_ERROR
end

function key:new(object)
    object = object or {}
    self.__index = self
    setmetatable(object, self)
    return object
end

function key:read_byte()
    local ch = io.read(1)
    read_keys[#read_keys + 1] = ch
    if ch == "\x1b" then    -- escape sequence
        local next_ch = io.read(1)
        while next_ch do
            read_keys[#read_keys + 1] = next_ch
            if #read_keys == 3 then break end
            next_ch = io.read(1)
        end
    end
end

function key:write_byte()
    local buf = ""
    for k, v in ipairs(read_keys) do
        buf = buf .. string.format("0x%02X ", string.byte(v))
        read_keys[k] = nil
    end
    io.write(buf .. "\r\n")
end

function key:process(screen, text)
    local key_type = what_type()

    if key_type == KEY_ERROR then
        -- nothing
    elseif key_type == KEY_COMMON then
        text:add_char(screen.cursor_x + screen.row_off, read_keys[1], screen.cursor_y + screen.col_off)
        screen:move_cursor_right(text)
    elseif key_type == KEY_ENTER then
        text:new_line(screen.cursor_x + screen.row_off, screen.cursor_y + screen.col_off)
        screen:move_cursor_down(text)
        screen.cursor_y = 1
    elseif key_type == KEY_ARROW_DOWN then
        screen:move_cursor_down(text)
    elseif key_type == KEY_ARROW_UP then
        screen:move_cursor_up(text)
    elseif key_type == KEY_ARROW_LEFT then
        screen:move_cursor_left()
    elseif key_type == KEY_ARROW_RIGHT then
        screen:move_cursor_right(text)
    elseif key_type == KEY_TAB then
        local whitespaces = string.rep(" ", config.tabsize)
        text:add_char(screen.cursor_x + screen.row_off, whitespaces, screen.cursor_y + screen.col_off)
        for i = 1, config.tabsize do screen:move_cursor_right(text) end
    elseif key_type == KEY_BACKSPACE then
        local remove_x = screen.row_off + screen.cursor_x
        local remove_y = screen.col_off + screen.cursor_y
        -- Check if we are at the beginning of the first row (merging rows)
        if screen.cursor_y + screen.col_off == 1 then
            -- Check if we are not at the first row (i.e., row_off + cursor_x > 1)
            if screen.row_off + screen.cursor_x > 1 then
                -- If the cursor is at the first character of the current row
                if screen.cursor_x == 1 then
                    -- Move to the previous row (decrease row offset)
                    screen.row_off = screen.row_off - 1
                else
                    -- Otherwise, move the cursor left (decrease cursor_x)
                    screen.cursor_x = screen.cursor_x - 1
                end
                -- Move the cursor vertically to the last character of the current row
                screen.cursor_y = #text[screen.cursor_x] + 1
            end
        else
            -- Otherwise, check if column offset is zero (meaning we are at the start of the row)
            if screen.col_off == 0 then
                -- If the cursor is at the first character of the current row, move up (decrease cursor_y)
                screen.cursor_y = screen.cursor_y - 1
            else
                -- If the cursor is not at the first character of the row
                if screen.cursor_y == 1 then
                    -- If the cursor is at the first column, decrease the column offset
                    screen.col_off = screen.col_off - 1
                else
                    -- Otherwise, move the cursor left (decrease cursor_y)
                    screen.cursor_y = screen.cursor_y - 1
                end
            end
        end
        text:remove_char(remove_x, remove_y)
    elseif key_type == KEY_SAVE then
        config.event = "s"
    elseif key_type == KEY_EXIT then
        config.event = "e"
        return 1
    end

    screen:move_cursor_to(screen.cursor_x, screen.cursor_y)
    screen:exec_ansi()

    for k, _ in ipairs(read_keys) do
        read_keys[k] = nil
    end

    return 0
end

return key